package edu.fourtwelve.util;

import java.security.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.*;

/**
 *
 * @author Christopher C. Hunt
 */
public class AsymetricKeyGen implements BankingConstants {

    private static final Logger log = Logger.getLogger(AsymetricKeyGen.class.getName());
    private final int KEY_SIZE = 2048;
    private final PrivateKey prKey;
    private final PublicKey puKey;

    public AsymetricKeyGen() throws BankingException {
        KeyPairGenerator keyPairGen;
        SecureRandom randomInit;
        KeyPair keyPair = null;
        try {
            keyPairGen = KeyPairGenerator.getInstance(BASE_ASYMETRIC_ALGORITHM);
            
            randomInit = SecureRandom.getInstance("SHA1PRNG");
            byte[] seed = randomInit.generateSeed(KEY_SIZE * 4);
            int[] msSeed = new int[KEY_SIZE];
            for(int i=0; i<KEY_SIZE * 4; i+=4) {
                msSeed[i%4] = Utils.byteArrayToInt(new byte[] {seed[i], seed[i+1], seed[i+2], seed[i+3]});
            }
            MersenneTwister msRand = new MersenneTwister(msSeed);
            msRand.nextBytes(seed);
            randomInit.setSeed(seed);
            
            keyPairGen.initialize(KEY_SIZE, randomInit);
            keyPair = keyPairGen.genKeyPair();
        } catch (NoSuchAlgorithmException ex) {
            throw new BankingException("Failure to create Pu/Pr Keys", ex);
        }
        
        prKey = keyPair.getPrivate();
        puKey = keyPair.getPublic();
    }

    public AsymetricKeyGen(PrivateKey key) {
        prKey = key;
        puKey = null;
    }

    public AsymetricKeyGen(PublicKey key) {
        prKey = null;
        puKey = key;
    }

    public byte[] encrypt(int mode, byte[] dataToEncrypt) throws BankingException {
        byte[] retBytes = null;
        try {
            Key key = null;

            switch (mode) {
                case 0:
                    key = puKey;
                    break;
                case 1:
                    key = prKey;
                    break;
                default:
                    throw new BankingException("Bad Input Mode");
            }

            Cipher cipher = Cipher.getInstance(ASYMETRIC_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, key);

            byte[] encrypted = new byte[cipher.getOutputSize(dataToEncrypt.length)];
            int enc_len = cipher.update(dataToEncrypt, 0, dataToEncrypt.length, encrypted, 0);
            enc_len += cipher.doFinal(encrypted, enc_len);

            retBytes = new byte[enc_len];
            System.arraycopy(encrypted, 0, retBytes, 0, enc_len);
        } catch (NoSuchAlgorithmException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (NoSuchPaddingException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (InvalidKeyException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (ShortBufferException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (IllegalBlockSizeException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (BadPaddingException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        }
        return retBytes;
    }

    public byte[] decrypt(int mode, byte[] dataToDecrypt) throws BankingException {
        byte[] retBytes = null;
        try {
            Key key = null;

            switch (mode) {
                case 0:
                    key = puKey;
                    break;
                case 1:
                    key = prKey;
                    break;
                default:
                    throw new BankingException("Bad Input Mode");
            }

            Cipher cipher = Cipher.getInstance(ASYMETRIC_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, key);

            byte[] decrypt = new byte[cipher.getOutputSize(dataToDecrypt.length)];
            int dec_len = cipher.update(dataToDecrypt, 0, dataToDecrypt.length, decrypt, 0);
            dec_len += cipher.doFinal(decrypt, dec_len);

            retBytes = new byte[dec_len];
            System.arraycopy(decrypt, 0, retBytes, 0, dec_len);


        } catch (NoSuchAlgorithmException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (NoSuchPaddingException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (InvalidKeyException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (ShortBufferException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (IllegalBlockSizeException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (BadPaddingException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        }
        return retBytes;
    }

    public synchronized PrivateKey getPrivateKey() {
        return prKey;
    }

    public synchronized PublicKey getPublicKey() {
        return puKey;
    }

    public static byte[] encrypt(Key key, int mode, byte[] dataToEncrypt) throws BankingException {
        byte[] retBytes = null;
        try {

            Cipher cipher = Cipher.getInstance(ASYMETRIC_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, key);

            byte[] encrypted = new byte[cipher.getOutputSize(dataToEncrypt.length)];
            int enc_len = cipher.update(dataToEncrypt, 0, dataToEncrypt.length, encrypted, 0);
            enc_len += cipher.doFinal(encrypted, enc_len);

            retBytes = new byte[enc_len];
            System.arraycopy(encrypted, 0, retBytes, 0, enc_len);
        } catch (NoSuchAlgorithmException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (NoSuchPaddingException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (InvalidKeyException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (ShortBufferException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (IllegalBlockSizeException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (BadPaddingException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        }
        return retBytes;
    }

    public static byte[] decrypt(Key key, int mode, byte[] dataToDecrypt) throws BankingException {
        byte[] retBytes = null;
        try {

            Cipher cipher = Cipher.getInstance(ASYMETRIC_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, key);

            byte[] decrypt = new byte[cipher.getOutputSize(dataToDecrypt.length)];
            int dec_len = cipher.update(dataToDecrypt, 0, dataToDecrypt.length, decrypt, 0);
            dec_len += cipher.doFinal(decrypt, dec_len);

            retBytes = new byte[dec_len];
            System.arraycopy(decrypt, 0, retBytes, 0, dec_len);


        } catch (NoSuchAlgorithmException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (NoSuchPaddingException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (InvalidKeyException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (ShortBufferException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (IllegalBlockSizeException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        } catch (BadPaddingException ex) {
            log.log(Level.SEVERE, null, ex);
            throw new BankingException(ex.getMessage());
        }
        return retBytes;
    }
}
